package com.snlab;

import java.util.ArrayList;
import java.util.Comparator;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.TreeSet;

import com.google.common.collect.BiMap;
import com.google.common.collect.HashBiMap;


public class APKeep {
    // public Set<Rule> rules = new TreeSet<Rule>((x, y) -> {if (x.prefix==y.prefix) {return x.hit - y.hit;}return y.prefix - x.prefix;} );
    public List<Rule> rules = new ArrayList<Rule>();
    public BDDEngine bddEngine = new BDDEngine(32);
    public BiMap<Port, Integer> ppm = HashBiMap.create();
    public HashMap<String, Port> ports = new HashMap<>();

    public APKeep() {
        ppm.put(getPort("drop"), 1);
    }

    public Port getPort(String name) {
        if (ports.get(name) == null) {
            ports.put(name, new Port(name));
        }
        return ports.get(name);
    }

    public void insert(List<Rule> batch) {
        batch.sort((x, y) -> y.prefix - x.prefix);
        Map<Port, Integer> deltaM = new HashMap<>();
        for (Rule r : batch) {
            this.insert(r, deltaM);
        }
        updateModel(deltaM);
    }

    public Map<Port, Integer> insert(Rule rule, Map<Port, Integer> deltaM) {
        // ArrayList<Change> changes = new ArrayList<>();
        
        for (Rule rp : rules) {
            if (bddEngine.and(rp.hit, rule.hit) != 0) {
                if (rp.prefix > rule.prefix) {
                    rule.hit = bddEngine.and(rule.hit, bddEngine.not(rp.hit));
                }
                if (rp.prefix < rule.prefix) {
                    if (rp.port != rule.port) {
                        int h = bddEngine.and(rp.hit, rule.hit);
                        // Change change = new Change(h, rp.port, rule.port);
                        // changes.add(change);
                        if (deltaM.get(rule.port) == null) {
                            deltaM.put(rule.port, 0);
                        }
                        deltaM.put(rule.port, bddEngine.or(deltaM.get(rule.port), h));
                    }

                    rp.hit = bddEngine.and(rp.hit, bddEngine.not(rule.hit));
                }
            }
        }
        rules.add(rule);
        return deltaM;
    }

    public void updateModel(Map<Port, Integer> deltaM) {
        for (Port pt : deltaM.keySet()) {
            int p = deltaM.get(pt);
            Iterator<Port> iterator = ppm.keySet().iterator();
            Map<Port, Integer> updates = new HashMap<>();
            while (iterator.hasNext()) {
                Port pt1 = iterator.next();
                int p1 = ppm.get(pt1);
                if (bddEngine.and(p, p1) != 0) {
                    updates.put(pt1, bddEngine.and(p1, bddEngine.not(p)));
                }
            }
            ppm.putAll(updates);
            if (!ppm.containsKey(pt)) ppm.put(pt, 0);
            ppm.put(pt, bddEngine.or(ppm.get(pt), p));
        }
        
    }

    void check() {
        for (Rule r : rules) {
            for (int p : ppm.values()) {
                if (bddEngine.and(r.hit, p) == r.hit) {
                    if (r.port != ppm.inverse().get(p)) {
                        System.out.println("error");
                        System.exit(1);
                    }
                }
            }
        }
    }

}
